package app.framework.config;

import app.framework.error.GlobalExceptionResolver;
import app.framework.security.authorization.CustomRbacAccessDecisionStrategy;
import app.framework.security.CustomSpringSecurityConfiguration;
import app.framework.security.authorization.FrontPageSupportUrlPatternMatcher;
import app.framework.security.authentication.JsonApiSupportAuthenticationEntryPoint;
import app.framework.security.captcha.CaptchaFilter;
import app.framework.security.authentication.AuthenticationFailureJsonResultHandler;
import app.framework.security.authentication.AuthenticationSuccessJsonResultHandler;
import app.framework.security.authentication.CsrfSupportRememberMeServices;
import app.framework.security.csrf.CsrfTokenUtil;
import app.framework.security.service.DaoSecuritySubjectService;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.Digester;
import jasmine.autoconfigure.security.JasmineSecurityAutoConfiguration;
import jasmine.autoconfigure.security.JasmineSecurityProperties;
import jasmine.autoconfigure.security.template.SpringSecurityConfigsTemplate;
import jasmine.framework.common.util.StringUtil;
import jasmine.security.rbac.dao.SecFunctionDAO;
import jasmine.security.rbac.dao.SecResourceDAO;
import jasmine.security.strategy.CacheRbacQueryService;
import jasmine.security.strategy.RbacAccessDecisionStrategy;
import jasmine.security.strategy.RbacQueryService;
import jasmine.security.strategy.UrlPatternMatcher;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.http.HttpMethod;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import java.util.UUID;

/**
 * <p>
 * 注册框架相关的 bean。
 * </p>
 *
 * @author mh.z
 */
@AutoConfigureBefore({JasmineSecurityAutoConfiguration.class, RedisAutoConfiguration.class})
@Configuration
public class FrameworkBeanConfig implements InitializingBean {
    private JasmineSecurityProperties securityProperties;
    private FrontGlobalConfig frontConfig;

    public FrameworkBeanConfig(JasmineSecurityProperties securityProperties, FrontGlobalConfig frontConfig) {
        this.securityProperties = securityProperties;
        this.frontConfig = frontConfig;
    }

    @Override
    public void afterPropertiesSet() {
        Digester digester = new Digester(DigestAlgorithm.MD5);

        String salt = frontConfig.getCsrfTokenSalt();
        if (StringUtil.isNotEmpty(salt)) {
            digester.setSalt(StringUtil.bytes(salt));
        }

        // 初始工具类
        CsrfTokenUtil.initUtil(digester);
    }

    /**
     * URL 模式匹配器
     *
     * @return
     */
    @Bean
    public UrlPatternMatcher urlPatternMatcher() {
        return new FrontPageSupportUrlPatternMatcher();
    }

    /**
     * 全局异常处理
     *
     * @return
     */
    @Bean
    public GlobalExceptionResolver globalExceptionResolver() {
        return new GlobalExceptionResolver();
    }

    /**
     * 扩展 Spring Security 配置
     *
     * @param securityProperties
     * @return
     */
    @Bean
    public SpringSecurityConfigsTemplate springSecurityConfigsTemplate(JasmineSecurityProperties securityProperties, FrontGlobalConfig frontConfig) {
        return new CustomSpringSecurityConfiguration(securityProperties, frontConfig);
    }

    /**
     * 自定义角色访问控制策略
     *
     * @param functionDAO
     * @param resourceDAO
     * @param urlPatternMatcher
     * @return
     */
    @Bean
    public RbacAccessDecisionStrategy accessDecisionStrategy(SecFunctionDAO functionDAO, SecResourceDAO resourceDAO, UrlPatternMatcher urlPatternMatcher) {
        RbacQueryService rbacQueryService = new CacheRbacQueryService(functionDAO, resourceDAO);
        return new CustomRbacAccessDecisionStrategy(rbacQueryService, urlPatternMatcher);
    }

    /**
     * 查询认证相关数据的服务
     *
     * @return
     */
    @Bean
    public DaoSecuritySubjectService daoSecuritySubjectService() {
        return new DaoSecuritySubjectService();
    }

    /**
     * 自定义未登录或会话超时返回的结果格式
     *
     * @return
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        String loginUrl = securityProperties.getFormLogin().getLoginPage();

        return new JsonApiSupportAuthenticationEntryPoint(loginUrl);
    }

    /**
     * Spring Security 登录成功的处理器
     *
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        String usernameParameter = securityProperties.getFormLogin().getUsernameParameter();

        return new AuthenticationSuccessJsonResultHandler(usernameParameter);
    }

    /**
     * Spring Security 登录失败的处理器
     *
     * @return
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        String usernameParameter = securityProperties.getFormLogin().getUsernameParameter();

        return new AuthenticationFailureJsonResultHandler(usernameParameter);
    }

    /**
     * 自定义"记住我"逻辑
     *
     * @param userDetailsService
     * @return
     */
    @Bean
    public RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
        JasmineSecurityProperties.RememberMe rememberMe = securityProperties.getRememberMe();
        String key = rememberMe.getKey();

        if (StringUtil.isEmpty(key)) {
            key = UUID.randomUUID().toString();
        }

        return new CsrfSupportRememberMeServices(key, userDetailsService);
    }

    /**
     * 验证码过滤器
     *
     * @return
     */
    @Bean
    public CaptchaFilter captchaFilter(JasmineSecurityProperties securityProperties) {
        JasmineSecurityProperties.FormLogin formLogin = securityProperties.getFormLogin();
        String loginProcessingUrl = formLogin.getLoginProcessingUrl();

        RequestMatcher requestMatcher = new AntPathRequestMatcher(loginProcessingUrl, HttpMethod.POST.name());
        CaptchaFilter captchaFilter = new CaptchaFilter(requestMatcher);

        return captchaFilter;
    }

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setKeySerializer(RedisSerializer.string());
        template.setConnectionFactory(redisConnectionFactory);

        return template;
    }

}
